package SSF.OS.UDP;
/**
 * udpSocket.java
 *
 * Created: Sat Aug 28 14:17:51 1999 hongbol
 * Revised Thu Nov 4 1999 ato
 * Revised Fri May 18 11:54:41 ato
 */

import SSF.OS.*;
import SSF.OS.UDP.*;
import SSF.OS.Socket.*;

  /** The four socket variables: local_ip, remote_ip, local_port, remote_port
   *  identify an end2end UDP session,
   *  if at least the local_port, remote_ip and remote_port have valid values.
   *  Else if only local_port (and optionally local_ip) are valid,
   *  while remote_ip = remote_port = -1, this is a local
   *  'listening server' socket.
   *  <P>The socket variables last_remote_ip, last_remote_port, last_local_ip
   *  store the source and destination of the last received UDP/IP
   *  header if this is a 'listening server socket', and they can be
   *  retrieved by the application that opened this socket.
   * 
   *  <P>UDP has no flow control, therefore for a UDP socket the write()
   *  operation sends nbytes of data (virtual or an object)
   *  immediately in a single UDP datagram if nbytes <= max_datagram_size
   *  set by DML attribute udpinit.max_datagram_size.
   *  <P>The UDP socket write() Continuation returns immediately after
   *  the datagram was passed through a chain of push() (or equivalent)
   *  methods to UDP, IP, and NIC; with a success() if the datagram was
   *  successfuly placed in the NIC output queue, and with the failure()
   *  if the datagram was dropped by either UDP (nbytes > max_datagram_size),
   *  or IP (no route to remote_ip), or by NIC (output queue overflow).
   *  Thus write() blocks only for zero elapsed simulation time.     
   *  
   *  <P>However, UDP socket read() blocks until it receives the requested
   *  nbytes of virtual bytes. The socket owner may request to read
   *  an arbitrary number of virtual bytes (greater than max_datagram_size).
   *  SSF.OS.UDP application writers should keep in mind that since
   *  UDP is unreliable, the read() might block forever.
   */
public class udpSocket implements socketAPI {

  /********************* Attribute Variables *********************/

  /** local UDP session associated with this socket */
  public udpSession udpsess;

  public udpSessionMaster udpsessMaster;

  /** local application session that created this socket */
  public ProtocolSession appsess;

  // we initalize these to -1 to help distinguish a fully configured
  // socket from an "unconnected" listening server socket
  public int local_ip = -1;
  public int remote_ip = -1;
  public int local_port = -1;
  public int remote_port = -1;

  // variables to save the source and dest variables of the last received
  // UDP/IP headers to pass them to the owner application if required
  public int last_remote_ip;
  public int last_local_ip;
  public int last_remote_port;

  // empty dataMessage for sending virtual data can be safely recycled
  // because it is not a payload of a UDP_Header
  public dataMessage emptyMsg;

  int  rcv_buf_len = 0;

  // only one blocking read() can be in progress at any time
  Continuation readCont = null;
  int readSize;
  Object[] dataObj;

  /************************* Constructors ************************/

  public udpSocket(ProtocolSession caller, socketMaster sm) {
    appsess = caller;
    udpsessMaster = sm.udpMaster;
    emptyMsg = new dataMessage(null, 0);
  }

  /************************ Class Methods ***********************/

  /** names a local socket. If local_ip = -1, a valid local host
   *  interface address will be assigned by SSF.OS.IP to datagrams sent via
   *  this socket after a UDP session is created via listen() or connect().
   */
  public void bind(int my_ip, int my_port){
    local_ip = my_ip;
    local_port = my_port;
  }

  /** after bind(), create a UDP listening session so that read() may be used.
   * request_limit is not implemented.
   */
  public void listen(int request_limit) {
    if(local_port == -1) {
       System.err.println("udpSocket Fatal Error: bind() local port before listen().");
       System.exit(-1);
    }
    udpsess = udpsessMaster.openSession(this);
  }

  /** sets remote IP and UDP port number and creates a UDP session so that
   *  socket write() may be used to send UDP datagrams - non-blocking.
   */ 
  public void connect(int dest_ip, int dest_port, Continuation caller)
    throws ProtocolException {
    remote_ip = dest_ip;
    remote_port = dest_port;  
    udpsess = udpsessMaster.openSession(this);
  }

  /** not supported for udp sockets */
  public void accept(socketAPI[] new_socket, Continuation caller) {
    System.err.println("Warning: udpSocket does not implement accept().");
  }

  public void write(Object[] obj, int nbytes, Continuation caller){
    if(nbytes > udpsessMaster.max_datagram_size) {
      caller.failure(1);
      return;
    }
    if (obj[0] == null) {
       System.err.println("Warning: udpSocket.write() null object - send as virtual data.");
       write(nbytes, caller);
       return;
    }
    dataMessage dmsg = new dataMessage(obj[0], nbytes);
    try {
      if (udpsess.push(dmsg, null))
        caller.success();
      else
        caller.failure(2);
    }
    catch (ProtocolException e) { System.err.println(e);}
  }

  /** not supported for udp sockets */
  public void write(byte[] buf, int nbytes, Continuation caller) {
    System.err.println("Warning: udpSocket does not implement byte write().");
  }

  public void write(int nbytes, Continuation caller){
    if(nbytes > udpsessMaster.max_datagram_size) {
      caller.failure(1);
      return;
    }
    emptyMsg.size = nbytes;
    try {
      if (udpsess.push(emptyMsg, null))
        caller.success();
      else
        caller.failure(2);
    }
    catch (ProtocolException e) { System.err.println(e);}
  }

  /** not supported for udp sockets */
  public void read(byte[] buf, int nbytes,  Continuation caller) {
    System.err.println("Warning: udpSocket does not implement byte read().");
  }

  /** not supported for udp sockets */
  public void abort() {
    System.err.println("Warning: udpSocket does not implement abort().");
  }

  public void read(Object[] obj, int nbytes,  Continuation caller) {
    if(readCont == null) {
      readCont = caller;
      readSize = nbytes;
      dataObj = obj;
    } else {
      System.err.println("udpSocket: read request error - socket already reading");
    } 
  }

  public void read(int nbytes, Continuation caller){
    if(readCont == null) {
      readCont = caller;
      readSize = nbytes;
      dataObj = null;
    } else {
      System.err.println("udpSocket: read request error - socket already reading");
    } 
  }
  
  public void close(Continuation caller)throws ProtocolException{
    udpsessMaster.closeSession(udpsess);
  }

  public void messageBuffer(dataMessage msg) {
    Continuation temp;

    // ignore incoming messages if nobody is reading from socket
    if (readCont == null)
      return;

    rcv_buf_len += msg.size;
    if((msg.data != null) && (dataObj != null))
      dataObj[0] = msg.data;

    if(readSize <= rcv_buf_len){
      rcv_buf_len -= readSize;
      temp = readCont;
      readCont = null;
      temp.success();
    }
  }
}
